ICollection

Rapid overview

ICollection — Countable, Modifiable Collections

"Use ICollection when you need Count without enumeration, and callers may Add/Remove items."

❌ Bad example:

public IEnumerable<Position> GetPositions()
{
    return _positions.Where(p => p.IsOpen);
}

// Caller
public void ValidatePositions(IEnumerable<Position> positions)
{
    int count = positions.Count(); // enumerates entire sequence
    if (count > MaxPositions)
        throw new InvalidOperationException("Too many positions");
}

Using .Count() on IEnumerable enumerates the whole sequence—inefficient for validation.

✅ Good example:

public ICollection<Position> GetPositions()
{
    return _positions.Where(p => p.IsOpen).ToList();
}

// Caller
public void ValidatePositions(ICollection<Position> positions)
{
    if (positions.Count > MaxPositions) // O(1) property access
        throw new InvalidOperationException("Too many positions");
}

👉 ICollection.Count is a property (O(1) for most collections), not an enumeration.

🔥 When Add/Remove matters:

public interface IOrderQueue
{
    ICollection<Order> PendingOrders { get; }
}

public class OrderProcessor
{
    private readonly IOrderQueue _queue;

    public void CancelOrder(int orderId)
    {
        var order = _queue.PendingOrders.FirstOrDefault(o => o.Id == orderId);
        if (order != null)
            _queue.PendingOrders.Remove(order); // mutation supported
    }
}

👉 Callers can modify the collection via Add/Remove without exposing concrete type.

💡 In trading systems:

  • Use ICollection for position snapshots where count validation is critical.
  • Expose ICollection when callers need to add/remove pending orders or alerts.
  • Prefer IEnumerable if Count isn't needed—keeps API minimal.

---

Questions & Answers

Q: What's the difference between IEnumerable and ICollection?

A: ICollection extends IEnumerable and adds Count, Add(), Remove(), Clear(), Contains(), and CopyTo(). Use ICollection when these operations are needed.

Q: Does ICollection guarantee O(1) Count?

A: Not strictly, but most implementations (List, HashSet) cache Count. LinkedList also has O(1) Count. IEnumerable.Count() enumerates everything.

Q: Should I return ICollection from methods?

A: Only if callers legitimately need Count or Add/Remove. Otherwise, IEnumerable is preferable—narrower contract, better encapsulation.

Q: Can I expose ICollection without allowing modification?

A: Use IReadOnlyCollection instead. It provides Count but no Add/Remove, preventing unintended mutations.

Q: What's the risk of returning ICollection?

A: Callers can mutate the collection, potentially breaking invariants. Return defensive copies or IReadOnlyCollection if modification isn't intended.

Q: How does ICollection relate to arrays?

A: Arrays implement ICollection, but Add/Remove throw NotSupportedException because arrays are fixed-size. Check IsReadOnly before assuming mutability.

Q: When should I use HashSet vs List for ICollection?

A: HashSet when uniqueness matters and Contains() is frequent (O(1)). List when order matters and indexed access is needed. Both implement ICollection.

Q: Can ICollection be lazy like IEnumerable?

A: No. Count property implies materialization. Returning ICollection signals that data is already loaded and countable.

Q: How do I mock ICollection in tests?

A: Use List or HashSet directly, or create a custom fake. Most mocking frameworks (Moq, NSubstitute) can stub ICollection methods.

Q: What's the performance of Contains() on ICollection?

A: Depends on implementation. HashSet is O(1), List is O(n). Always consider the concrete type when performance matters, even if accepting ICollection.